package cn.edu.xmu.orderpayment.payment.Service;

import cn.edu.xmu.oomall.core.util.ReturnNo;
import cn.edu.xmu.oomall.core.util.ReturnObject;
import cn.edu.xmu.oomall.core.util.Common;
import cn.edu.xmu.orderpayment.payment.Dao.PaymentDao;
import cn.edu.xmu.orderpayment.payment.Dao.PaymentPatternDao;
import cn.edu.xmu.orderpayment.payment.Dao.RefundDao;
import cn.edu.xmu.orderpayment.payment.constants.Constants;
import cn.edu.xmu.orderpayment.payment.mapper.PaymentPoMapper;
import cn.edu.xmu.orderpayment.payment.model.po.PaymentPoExample;
import cn.edu.xmu.orderpayment.payment.util.PayMentPatternLoader;
import cn.edu.xmu.orderpayment.payment.microservice.alipayvo.NotifyBody;
import cn.edu.xmu.orderpayment.payment.microservice.alipayvo.OrderInfoVo;
import cn.edu.xmu.orderpayment.payment.microservice.service.OrderService;
import cn.edu.xmu.orderpayment.payment.microservice.wechatpayvo.WeChatPayRefundNotifyRetVo;
import cn.edu.xmu.orderpayment.payment.microservice.wechatpayvo.WeChatPayTransactionRetVo;
import cn.edu.xmu.orderpayment.payment.model.bo.Amount;
import cn.edu.xmu.orderpayment.payment.model.bo.Payment;
import cn.edu.xmu.orderpayment.payment.model.bo.PaymentPattern;
import cn.edu.xmu.orderpayment.payment.model.bo.Refund;
import cn.edu.xmu.orderpayment.payment.model.po.PaymentPo;
import cn.edu.xmu.orderpayment.payment.model.vo.*;
import cn.edu.xmu.orderpayment.payment.mq.RocketMQService;
import cn.edu.xmu.orderpayment.payment.util.baseobject.DoPayInfo;
import cn.edu.xmu.orderpayment.payment.util.paypattern.BasePaymentPattern;
import cn.edu.xmu.privilegegateway.annotation.util.JacksonUtil;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cn.edu.xmu.privilegegateway.annotation.util.Common.cloneVo;
import static cn.edu.xmu.privilegegateway.annotation.util.Common.setPoCreatedFields;

/**
 * @author Guanlin Wang 22920192204282
 * @date 2021/12/12
 * Service层负责业务逻辑
 */
@Service
public class PaymentService {

    @Autowired(required = false)
    PaymentPoMapper paymentPoMapper;

    @Autowired
    private PaymentDao paymentDao;

    @Autowired
    private RefundDao refundDao;

    @Autowired
    private PaymentPatternDao paymentPatternDao;

    @Autowired
    @Lazy
    private PayMentPatternLoader payMentPatternLoader;

    @Autowired
    private RocketMQService rocketMQService;

    private Logger logger = LoggerFactory.getLogger(PaymentService.class);

    /**
     * 用户支付已建立的订单
     * 如果不是自己的订单出507错误
     * 如果是预售订单, 需要支付两次
     *
     * @param pid
     * @param patternId
     * @param loginUserId
     * @param loginUserName
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public ReturnObject payOrderPayment(Long pid, Long patternId, Long loginUserId, String loginUserName) {
        //PO(Persistent Object): 此对象与数据库表结构一一对应, 通过Dao层向上传输数据源对象
        ReturnObject<Payment> ret = paymentDao.getPaymentById(pid);//通过母单id查找支付单详情
        if (ret.getData() == null) return ret;
        Payment payment = ret.getData();//支付单存在, 获取data
        // 因为判断订单的归属具有业务逻辑 所以放到Service层中校验
        if (payment.getState() != Payment.State.PENDING_PAYMENT) return new ReturnObject<>(ReturnNo.STATENOTALLOW, "当前状态不允许支付");
        if (payment.getEndTime().isBefore(LocalDateTime.now()))
            return new ReturnObject(ReturnNo.STATENOTALLOW, "支付单时间已过");
        String documentId = payment.getDocumentId();
        // 通过id查找支付渠道
        ReturnObject<PaymentPattern> ret1 = paymentPatternDao.getPaymentPatternById(patternId);
        if (ret1.getData() == null) return ret1;
        // 给payment设置支付渠道
        payment.setPatternId(patternId);
        ReturnObject<PaymentPo> paymentPoReturnObject = paymentDao.updatePayment(payment, loginUserId, loginUserName);
        String className = ret1.getData().getClassName();
        try {
            BasePaymentPattern pattern = payMentPatternLoader.getPaymentPatternByClassName(className);
            pattern.pay(documentId, new Amount(null, payment.getAmount(), "CNY"), payment.getDescr());//具体的支付

            String json = JacksonUtil.toJson(payment);
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            Constants.servletRequestAttributes = servletRequestAttributes;
            rocketMQService.sendPayCheckMessage(json, Constants.FIVE_SECONDS_LEVEL);
            return new ReturnObject<>(cloneVo(paymentPoReturnObject.getData(), NewPaymentRetVo.class));
        }catch (Exception e) {
            logger.error(e.getMessage());
            return new ReturnObject(ReturnNo.INTERNAL_SERVER_ERR, e.getMessage());
        }
    }


    /**
     * 顾客支付
     *
     * @param payBody
     * @param loginUserId
     * @param loginUserName
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public ReturnObject createPayment(PayBody payBody, Long loginUserId, String loginUserName) {
        Payment payment = cloneVo(payBody, Payment.class);
        payment.setState(Payment.State.PENDING_PAYMENT);
        setPoCreatedFields(payment, loginUserId, loginUserName);
        ReturnObject<Payment> paymentReturnObject = paymentDao.createPayment(payment);
        Payment target = paymentReturnObject.getData();

        ReturnObject<PaymentPattern> ret = paymentPatternDao.getPaymentPatternById(payBody.getPatternId());
        if (ret.getData() == null) return ret;
        PaymentPattern paymentPattern = ret.getData();
        String className = paymentPattern.getClassName();

        try {
            BasePaymentPattern pattern = payMentPatternLoader.getPaymentPatternByClassName(className);

            DoPayInfo doPayInfo = pattern.pay(String.valueOf(target.getId()), new Amount(null, payBody.getAmount(), "CNY"), payBody.getDescr());

            // 发延迟消息检查第三方支付状态
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            Constants.servletRequestAttributes = servletRequestAttributes;
            String json = JacksonUtil.toJson(target);

            rocketMQService.sendPayCheckMessage(json, Constants.FIVE_SECONDS_LEVEL);

            NewPaymentRetVo newPaymentRetVo = cloneVo(target, NewPaymentRetVo.class);
            if (!(doPayInfo == null || doPayInfo.getPrepayId() == null))
                newPaymentRetVo.setPrepayId(doPayInfo.getPrepayId());
            return new ReturnObject<>(newPaymentRetVo);
        }catch (Exception e) {
            logger.error(e.getMessage());
            return new ReturnObject(ReturnNo.INTERNAL_SERVER_ERR, e.getMessage());
        }
    }

    /**
     * 平台管理员查询支付信息
     * 传入token校验权限
     * documentID匹配
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class, readOnly = true)
    public ReturnObject getPaymentInfo(String documentId, Byte state, ZonedDateTime beginTime, ZonedDateTime endTime, Integer page, Integer pageSize) {
        PaymentPoExample example = new PaymentPoExample();
        PaymentPoExample.Criteria criteria = example.createCriteria();
        if (documentId != null) {
            criteria.andDocumentIdEqualTo(documentId);
        }
        if (state != null) {
            criteria.andStateEqualTo(state);
        }
        if (beginTime != null) {
            criteria.andPayTimeGreaterThanOrEqualTo(beginTime.withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime());
        }
        if (endTime != null) {
            criteria.andPayTimeLessThanOrEqualTo(endTime.withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime());
        }

        ReturnObject res = paymentDao.getPaymentInfo(example, page, pageSize);
        return res;
    }

    /**
     * 平台管理员查询支付的详细信息
     *
     * @param id
     * @return
     */
    @Transactional(readOnly = true, rollbackFor = Exception.class)
    public ReturnObject adminGetCertainPaymentInfo(Long id) {
        return new ReturnObject(paymentDao.selectPaymentInfoByPrimaryKey(id));
    }

    /**
     * 平台管理员修改支付信息
     *
     * @param id
     * @param modificationBody
     * @param loginUserId
     * @param loginUserName
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public ReturnObject adminUpdatePaymentInfo(Long id, ModificationBody modificationBody, Long loginUserId, String loginUserName) {
        Payment bo = new Payment();
        //校验无误就为bo赋值
        if (modificationBody.getState() != null) {
            if (modificationBody.getState().equals(Payment.State.RECONCILIATION_COMPLETED.getCode()) || modificationBody.getState().equals(Payment.State.RECONCILIATION_COMPLETED.getCode())) {
                return new ReturnObject(ReturnNo.STATENOTALLOW, "当前状态禁止此操作");// 在业务逻辑层校验该支付单的状态
            }// 这样做是否正确?
            bo.setState(modificationBody.getState());
        }
        if (modificationBody.getDescr() != null) {
            bo.setDescr(modificationBody.getDescr());
        }
        bo.setId(id);
        return new ReturnObject(paymentDao.updatePayment(bo, loginUserId, loginUserName));
    }

    /**
     * 获取支付单的所有状态
     *
     * @return
     */
    @Transactional(readOnly = true, rollbackFor = Exception.class)
    public ReturnObject getPaymentStates() {
        List<Map<String, Object>> stateList;
        stateList = new ArrayList<>();
        for (Payment.State requestType : Payment.State.values()) {
            Map<String, Object> temp = new HashMap<>();
            temp.put("code", requestType.getCode());
            temp.put("name", requestType.getDescription());
            stateList.add(temp);
        }
        return new ReturnObject(stateList);
    }

    /**
     * 顾客查询订单支付信息
     *
     * @param id
     * @return
     */
    @Transactional(readOnly = true, rollbackFor = Exception.class)
    public ReturnObject customerGetPaymentInfo(String id) {
        return new ReturnObject(paymentDao.customerSelectPayment(id));
    }

    /**
     *
     * @param payPatternId
     * @return
     */
    /**
     * 微信支付回调通知
     * 对收到的支付结果，修改相应支付单信息
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public ReturnObject wechatPaymentNotify(WeChatPayTransactionRetVo weChatPayTransactionRetVo) {

        Payment payment = (Payment) paymentDao.selectPaymentInfoByPrimaryKey(Long.valueOf(weChatPayTransactionRetVo.getOutTradeNo())).getData();
        if (payment == null) {
            return new ReturnObject(ReturnNo.RESOURCE_ID_NOTEXIST);
        }
        if (!payment.getState().equals(Payment.State.PENDING_PAYMENT)) {
            return new ReturnObject(ReturnNo.STATENOTALLOW);
        }

        if (weChatPayTransactionRetVo.getTradeState().equals("SUCCESS")) {
            payment.setState(Payment.State.PAYMENT_COMPLETED.getCode());
            payment.setActualAmount(weChatPayTransactionRetVo.getAmount().getPayerTotal());
            payment.setPayTime(weChatPayTransactionRetVo.getSuccessTime());
            // 成功通知订单
            try {
                Thread.sleep(1000);
                rocketMQService.sendSuccessOrderPayMessage(payment.getDocumentId());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        if (weChatPayTransactionRetVo.getTradeState().equals("NOTPAY")) {
            payment.setState(Payment.State.CANCELED.getCode());
        }
        payment.setTradeSn(weChatPayTransactionRetVo.getTransactionId());
        payment.setGmtModified(LocalDateTime.now());

        paymentDao.updatePayment(payment, payment.getCreatorId(), payment.getCreatorName());
        return new ReturnObject();
    }


    /**
     * 阿里回调通知
     * 对收到的支付或退款结果，修改相应支付或退款单信息
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public ReturnObject alipayNotify(NotifyBody notifyBody) {

        if (notifyBody.getOut_biz_no() == null) {
            Payment payment = (Payment) paymentDao.selectPaymentInfoByPrimaryKey(Long.valueOf(notifyBody.getOut_trade_no())).getData();
            if (payment == null) {
                return new ReturnObject(ReturnNo.RESOURCE_ID_NOTEXIST);
            }
            if (!payment.getState().equals(Payment.State.PENDING_PAYMENT)) {
                return new ReturnObject(ReturnNo.STATENOTALLOW);
            }

            if (notifyBody.getTrade_status().equals("TRADE_SUCCESS")) {
                payment.setState(Payment.State.PAYMENT_COMPLETED.getCode());
                payment.setActualAmount(notifyBody.getBuyer_pay_amount());
                payment.setPayTime(notifyBody.getGmt_payment());
            }
            if (notifyBody.getTrade_status().equals("WAIT_BUYER_PAY")) {
                payment.setState(Payment.State.CANCELED.getCode());
            }
            payment.setGmtModified(LocalDateTime.now());

            paymentDao.updatePayment(payment, payment.getCreatorId(), payment.getCreatorName());
            return new ReturnObject();
        } else {
            Refund refund = (Refund) refundDao.selectRefundInfoByPrimaryKey(Long.valueOf(notifyBody.getOut_biz_no())).getData();
            if (refund == null) {
                return new ReturnObject(ReturnNo.RESOURCE_ID_NOTEXIST);
            }
            if (!refund.getState().equals(Refund.State.PENDING_PAYMENT)) {
                return new ReturnObject(ReturnNo.STATENOTALLOW);
            }

            refund.setState(Refund.State.PAYMENT_COMPLETED.getCode());
            refund.setRefundTime(notifyBody.getGmt_refund());
            refund.setGmtModified(LocalDateTime.now());

            refundDao.updateRefund(refund, refund.getCreatorId(), refund.getCreatorName());
            return new ReturnObject();
        }
    }

    @Transactional(rollbackFor = Exception.class, readOnly = true)
    public ReturnObject getPaymentByDocumentId(String documentId) {
        ReturnObject<List> paymentReturnObject = paymentDao.getPaymentByDocumentId(documentId);
        if (paymentReturnObject.getData() == null) return paymentReturnObject;
        ReturnObject ret = Common.getListRetVo(paymentReturnObject, InternalPaymentRetVo.class);
        return ret;
    }
}
